﻿// <copyright file="BingMaps.cs" company="Nizwig">
// Copyright (c) 2013 All Rights Reserved 
// </copyright>
// <author></author>
// <date>2/21/2013 3:14:27 PM</date>
// <summary>Implements the BingMaps Workflow Activity.</summary>
namespace GeoCodeWF.BingMaps
{

    //Help from MSDN Bing MAPS Articles: http://msdn.microsoft.com/en-us/library/hh534080.aspx 

    using System;
    using System.Activities;
    using System.ServiceModel;
    using Microsoft.Xrm.Sdk;
    using Microsoft.Xrm.Sdk.Workflow;

    using System.Xml;
    using System.Xml.XPath;
    using System.Net;
    using System.Web;
  

    public sealed class BingMaps : CodeActivity
    {

        // Workflow Params
        [Input("BingMapsKey")]
        public InArgument<string> BingMapsKey { get; set; }

        [Input("BingMapsGeoCodeURL")]
        [Default("http://dev.virtualearth.net/REST/v1/Locations?")]
        public InArgument<string> BingMapsGeoCodeURL { get; set; }

        [Input("Street")]
        public InArgument<string> Address_Street { get; set; }

        [Input("City")]
        public InArgument<string> Address_City { get; set; }

        [Input("State")]
        public InArgument<string> Address_State { get; set; }

        [Input("PostalCode")]
        public InArgument<string> Address_PostalCode { get; set; }

        //ISO Country Code - http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 
        [Input("Country")]
        [Default("US")]
        public InArgument<string> Address_Country { get; set; }

        [Output("GeocodeQuality")]
        public OutArgument<String> GeocodeQuality { get; set; }

        [Output("Latitude")]
        public OutArgument<double> Address_Latitude { get; set; }

        [Output("Longitude")]
        public OutArgument<double> Address_Longitude { get; set; }

        [Output("GeoCodeResult")]
        public OutArgument<string> GeoCode_Result { get; set; }

        private double latitude;
        private double longitude;
        private string geocodeQuality;
        private string result;
        private string Bing_Maps_Url = "http://dev.virtualearth.net/REST/v1/Locations?";

        /// <summary>
        /// Executes the workflow activity.
        /// </summary>
        /// <param name="executionContext">The execution context.</param>
        protected override void Execute(CodeActivityContext executionContext)
        {
            // Create the tracing service
            ITracingService tracingService = executionContext.GetExtension<ITracingService>();

            if (tracingService == null)
            {
                throw new InvalidPluginExecutionException("Failed to retrieve tracing service.");
            }

            tracingService.Trace("Entered BingMaps.Execute(), Activity Instance Id: {0}, Workflow Instance Id: {1}",
                executionContext.ActivityInstanceId,
                executionContext.WorkflowInstanceId);

            // Create the context
            IWorkflowContext context = executionContext.GetExtension<IWorkflowContext>();

            if (context == null)
            {
                throw new InvalidPluginExecutionException("Failed to retrieve workflow context.");
            }

            tracingService.Trace("BingMaps.Execute(), Correlation Id: {0}, Initiating User: {1}",
                context.CorrelationId,
                context.InitiatingUserId);

            IOrganizationServiceFactory serviceFactory = executionContext.GetExtension<IOrganizationServiceFactory>();
            IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);

            try
            {
                // TODO: Implement your custom Workflow business logic.
                //1.Build the Request based on data passed in.
                //2. geocode and stick in the output 

                //Create the REST Services 'Find Location by Query' request
                string _bingMapsKey = BingMapsKey.Get<string>(executionContext);
                string _bingMapsGeocodeURL = BingMapsGeoCodeURL.Get<string>(executionContext);
                string locationQuery = BuildRequestQuery(Address_Street.Get<string>(executionContext),
                                        Address_City.Get<string>(executionContext),
                                        Address_State.Get<string>(executionContext),
                                        Address_Country.Get<string>(executionContext),
                                        Address_PostalCode.Get<string>(executionContext));
                tracingService.Trace("Location Query : {0}", locationQuery);

                string locationsRequest = CreateRequest(locationQuery, _bingMapsGeocodeURL,  _bingMapsKey);
                tracingService.Trace("Request URL : {0}", locationsRequest);

                XmlDocument locationsResponse = MakeRequest(locationsRequest);
                tracingService.Trace("Location response : {0}", locationsResponse.ToString());

                tracingService.Trace("Process Response - BEGIN");
                //ProcessResponse(locationsResponse, ref latitude, ref longitude, ref geoCodeResult);
                ProcessResponse(locationsResponse);
                tracingService.Trace("Process Response - END");

                //publish the results of geoCoding
                Address_Latitude.Set(executionContext, latitude);
                Address_Longitude.Set(executionContext, longitude);
                GeocodeQuality.Set(executionContext, geocodeQuality.ToUpper());
                GeoCode_Result.Set(executionContext, locationsRequest + " \n\r ----- \n\r" + result);
                tracingService.Trace("Result of GeoCoding, Lat: {0}; Long: {1}; Result: {2}", latitude.ToString(), longitude.ToString(), result);
            }
            catch (FaultException<OrganizationServiceFault> e)
            {
                tracingService.Trace("Exception: {0}", e.ToString());

                // Handle the exception.
                throw;
            }

            tracingService.Trace("Exiting BingMaps.Execute(), Correlation Id: {0}", context.CorrelationId);
        }

        /// <summary>
        /// Build the Query for the BING Maps REST Service Location API.
        /// </summary>
        /// <param name="street"></param>
        /// <param name="city"></param>
        /// <param name="state"></param>
        /// <param name="country"></param>
        /// <param name="postalCode"></param>
        /// <returns></returns>
        private string BuildRequestQuery(string street, string city, string state, string country, string postalCode)
        {
            //Unstructured query:http://dev.virtualearth.net/REST/v1/Locations?CountryRegion=US&adminDistrict=WA&locality=Somewhere&postalCode=98001&addressLine=100%20Main%20St.&key=BingMapsKey
            //Find a Location by Query query: http://dev.virtualearth.net/REST/v1/Locations?CountryRegion=US&adminDistrict=WA&locality=Somewhere&postalCode=98001&addressLine=100%20Main%20St.&key=BingMapsKey
            //http://dev.virtualearth.net/REST/v1/Locations?countryRegion=countryRegion&adminDistrict=adminDistrict&locality=locality&postalCode=postalCode&addressLine=addressLine&userLocation=userLocation&userIp=userIp&usermapView=usermapView&includeNeighborhood=includeNeighborhood&maxResults=maxResults&key=BingMapsKey

            string locationQuery = string.Empty;
            //Minimum amt of data. You need at least PostalCode to provide a lat/long.
            locationQuery += "CountryRegion=" + country;
            //1. Check if PostalCode is not empty. I'm not doing a number check, since postalCode outside USA is alphanumeric. 
            if (!string.IsNullOrEmpty(postalCode))
                locationQuery += "&" + "postalCode=" + System.Uri.EscapeUriString(postalCode);

            //2. Check to see if State is Empty
            if (!String.IsNullOrEmpty(state))
                locationQuery += "&" + "adminDistrict=" + System.Uri.EscapeUriString(state);

            //3. Check to see if City is Empty
            if (!string.IsNullOrEmpty(city))
                locationQuery += "&" + "locality=" + System.Uri.EscapeUriString(city);

            //4. Check if Street address is empty
            if (!string.IsNullOrEmpty(street))
                locationQuery += "&" + "addressLine=" + System.Uri.EscapeUriString(street);

            return locationQuery;
        }

        /// <summary>
        /// Create REST Service Request
        /// </summary>
        /// <param name="queryString"></param>
        /// <param name="l_BingMapsKey"></param>
        /// <returns></returns>
        private string CreateRequest(string queryString, string geocodeURL, string l_BingMapsKey)
        {
            string UrlRequest = geocodeURL + queryString + "&output=xml" + " &key=" + l_BingMapsKey;
            return (UrlRequest);
        }

        /// <summary>
        /// Make REST Service Request
        /// </summary>
        /// <param name="requestUrl"></param>
        /// <returns></returns>
        private XmlDocument MakeRequest(string requestUrl)
        {
            try
            {
                HttpWebRequest request = WebRequest.Create(requestUrl) as HttpWebRequest;
                HttpWebResponse response = request.GetResponse() as HttpWebResponse;

                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.Load(response.GetResponseStream());
                return (xmlDoc);

            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);

                Console.Read();
                return null;
            }
        }


        /// <summary>
        /// Process REST Service Request. 
        /// </summary>
        /// <param name="locationsResponse"></param>
        //static public void ProcessResponse(XmlDocument locationsResponse, ref decimal latitude, ref decimal longitude, ref string result)
        private void ProcessResponse(XmlDocument locationsResponse)
        {

            //initialize before the assignments
            latitude = 0; longitude = 0; result = string.Empty;

            //Create namespace manager
            XmlNamespaceManager nsmgr = new XmlNamespaceManager(locationsResponse.NameTable);
            nsmgr.AddNamespace("rest", "http://schemas.microsoft.com/search/local/ws/rest/v1");

            //Get all locations that have a MatchCode=Good and Confidence=High
            XmlNodeList matchCodeGoodElements = locationsResponse.SelectNodes("//rest:Location/rest:MatchCode[.='Good']/parent::node()", nsmgr);

            //get the first Point node from GoodElements
            if (matchCodeGoodElements.Count > 0)
            {
                //TODO: You can fetch the 
                XmlNode latElement = locationsResponse.SelectSingleNode(".//rest:Location/rest:GeocodePoint[1]/rest:Latitude", nsmgr);
                XmlNode longElement = locationsResponse.SelectSingleNode(".//rest:Location/rest:GeocodePoint[1]/rest:Longitude", nsmgr);
                XmlNode geocodeQualityElement = locationsResponse.SelectSingleNode(".//rest:Location/rest:MatchCode", nsmgr);

                //Persist results in Notes entitiy and associate it with the workflow entity. 
                //latitude = 84.04693m;
                //longitude = -84.3204m;
                latitude = Convert.ToDouble(latElement.InnerText);
                longitude = Convert.ToDouble(longElement.InnerText);
                geocodeQuality = geocodeQualityElement.InnerText;
                XmlNodeList locList = locationsResponse.SelectNodes(".//rest:Location", nsmgr);
                result = locList.Item(0).InnerXml.Substring(0, ((locList.Item(0).InnerXml.Length > 2000) ? 1950 : (locList.Item(0).InnerXml.Length - 1)));
            }
        }        

    }
}